還記得之前我們實作 dependencies.rb
這個檔案的目的是什麼嗎?
# mavericks/lib/mavericks/dependencies.rb
class Object
def self.const_missing(const)
return nil if @called_const_missing
@called_const_missing = true
require Mavericks.to_underscore(const.to_s)
klass = Object.const_get(const)
@called_const_missing = false
klass
end
end
搭配在專案 just_do 的 config/application.rb
$LOAD_PATH << File.join(File.dirname(__FILE__), "..", "app", "controllers")
就可以做到 autoloading
的效果,不用每次新增一個 controller 的檔案就 require 一次,不過這樣做會有一些問題,原因是我們不應該處理所有的 const_missing
,這樣當其他套件沒處理 const_missing
的狀況時,反而被我們給 "處理" 了,簡單說就是管太多,所以我們今天的目標除了要修正這個問題,另外也將檔案整理成 Active Support
在 Rails 裡面 Active Support
是一個擴充的工具函式庫,意思就是當你用 Rails 來寫網站時,常常會用到的一些函式庫,這些函式庫是 Ruby 本身沒有支援或是說不夠完整(畢竟 Ruby 本身不是為了寫網站而做得語言),例如像是 Rails 的 autoloading
要設計成 Active Support
的樣子,就要把原先掛在 Mavericks 底下的 dependencies.rb
,搬移到 Active Support
底下,並且建立 Active Support
的資料夾,另外在 Mavericks 裡面,我們會處理一些文字的轉換,例如將 TasksController
轉成 tasks_controller
,所以我們要擴充 Ruby 的 String
依照規劃,預期的資料夾結構會成為這樣
.
├── lib
│ ├── active_support
│ │ ├── core_ext
│ │ │ └── string.rb
│ ├── active_support.rb
之前我們的寫法是這樣
# mavericks/lib/mavericks/support.rb
module Mavericks
def self.to_underscore(string)
string.gsub(/::/, '/')
.gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2')
.gsub(/([a-z\d])([A-Z])/,'\1_\2')
.tr("-", "_").downcase
end
def self.to_plural(string)
pattern = /.*s$|x$|z$|sh$|ch$/
pattern.match?(string) ? "#{string}es" : "#{string}s"
end
end
但這樣有個問題,就是每次要使用都要這樣呼叫
Mavericks.to_underscore(const.to_s)
有點不太直覺,我們希望可以直接在 Ruby 的字串上直接呼叫 to_underscore
這個方法,像是這樣
'TasksController'.to_underscore
但是 Ruby 的 String 不是已經定義好了嗎?這裡我們可以用 Open Classes
這個技巧,來做到新增 method 到已經定義好的 Class,舉個例子來說,我們可以幫字串建立一個方法叫 tell_me_size
class String
def tell_me_size
self.size
end
end
puts '鐵人30'.tell_me_size
這樣我們就可以新增或是複寫 method,知道原理以後,就來實作吧
我們在 string.rb
新增兩個 method
# mavericks/lib/active_support/core_ext/string.rb
class String
def to_underscore
self.to_s.gsub(/::/, '/')
.gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2')
.gsub(/([a-z\d])([A-Z])/,'\1_\2')
.tr("-", "_").downcase
end
def to_plural
str = self.to_s
pattern = /.*s$|x$|z$|sh$|ch$/
pattern.match?(str) ? "#{str}es" : "#{str}s"
end
end
接著打開 irb
來看看效果
2.6.6 :001 > require('./lib/active_support/core_ext/string.rb')
=> true
2.6.6 :002 > 'TasksController'.to_underscore
=> "tasks_controller"
2.6.6 :003 > 'task'.to_plural
=> "tasks"
這樣就完成 String 的擴充了!
接著我們修改一下 dependencies.rb
,開頭有說過我們原先的寫法有很大的問題就是「管太多」,所以我們要修正一下邏輯
module ActiveSupport
module Dependencies
extend self
attr_accessor :autoload_paths
self.autoload_paths = []
def search_for_file(name)
autoload_paths.each do |path|
file = File.join(path, "#{name}.rb")
if File.file? file
return file
end
end
nil
end
end
end
class Module
def const_missing(name)
if file = ActiveSupport::Dependencies.search_for_file(name.to_s.underscore)
require file.sub(/\.rb$/, '')
const_get name
else
raise NameError, "Uninitialized constant #{name}"
end
end
end
一樣我們用 const_missing
來攔截找不到常數
的狀況,接著我們加上一個屬性叫 autoload_paths
,來紀錄要搜尋常數的檔案位置,例如 app/controllers/
或是 app/models
之類的資料夾,這樣我們就可以過濾掉那些不在我們檔案範圍內的狀況
接著我們就可以把昨天的 ActiveRecord 裡面的 mavericks/support
拿掉
module ActiveRecord
# 拿掉原先的 support
# autoload :Mavericks, "mavericks/support"
autoload :Base, "active_record/base"
autoload :Persistence, "active_record/persistence"
autoload :ConnectionAdapter, "active_record/connection_adapter"
autoload :Relation, "active_record/relation"
end
然後原先在 Mavericks 有用到的 Mavericks.to_underscore
和 Mavericks.to_plural
都要做修正
# mavericks/lib/active_record/base.rb
def self.table_name
singular_table_name = name.to_s.to_underscore
singular_table_name.to_plural
end
# mavericks/lib/mavericks/controller.rb
def controller_name
klass = self.class
klass = klass.to_s.gsub /Controller$/, ""
klass.to_underscore
end
別忘了要在 active_support.rb
做 autoload,跟 active_record.rb
一樣
# mavericks/lib/active_support.rb
require 'active_support/core_ext/string'
module ActiveSupport
autoload :Dependencies, 'active_support/dependencies'
end
因為 string.rb 並不是在 module ActiveSupport
底下,所以還是要另外 require 進來
接著在 mavericks.rb 上面也要 require 新的程式碼進來
# mavericks/lib/mavericks.rb
require "mavericks/version"
require "active_support"
require "active_record"
require "mavericks/routing"
require "mavericks/controller"
.
.
(略)
接著我們回到久違的 just_do,來用我們小改版的 Mavericks,先在 config/application.rb
做修改,讓專案在初始化啟動時,就將需要的設定做載入
# just_do/config/application.rb
require 'mavericks'
# 建立連線
ActiveRecord::Base.establish_connection
# autoload 預設路徑
ActiveSupport::Dependencies.autoload_paths = Dir["./app/*"]
module JustDo
class Application < Mavericks::Application
end
end
在專案初始化時建立連線以外,還要給 autoload 的預設路徑,其中 autoload_paths
就是我們剛剛提到不要讓 const_miss
管太多的部分
接下來的用法就跟 Rails 一樣,我們新增一個 Model 的檔案
# just_do/app/models/task.rb
class Task < ActiveRecord::Base
end
然後修改一下 Controller
# just_do/app/controllers/tasks_controller.rb
class TasksController < Mavericks::Controller
def index
@tasks = Task.all
end
def show
@task = Task.find(params['id'])
end
end
要記得現在取出的 Model 要呼叫 Attribute 是這樣呼叫 task.title
,所以在 View
的檔案那邊也要做修改
# 原先的寫法
task['title']
# 改版後的寫法
task.title
然後啟動 server 看看現在專案可不可以執行起來,如果可以代表成功了!
如果不行可以參考我傳的 github 或是 debug 看看錯在那裏
https://github.com/apayu/mavericks